package br.ufc.ck0017.visitor.translation;

import br.ufc.ck0017.compiler.Translator;
import br.ufc.ck0017.frame.Frame;
import br.ufc.ck0017.ir.translate.TranslateEx;
import br.ufc.ck0017.ir.translate.TranslatedExpression;
import br.ufc.ck0017.ir.tree.BINOP.Operator;
import br.ufc.ck0017.ir.tree.IR;
import br.ufc.ck0017.ir.tree.IRNode;
import br.ufc.ck0017.syntaxtree.*;
import br.ufc.ck0017.temp.Temp;
import br.ufc.ck0017.visitor.Visitor;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class TranslationVisitor implements Visitor<TranslatedExpression> {
    // As the symbol table is not yet implemented, we naively assume every object to hold
    // only 512 bytes, at most.
    private static final int MAX_CLASS_LENGTH = 512;

    private String currentClassName;
    private String currentMethodName;
    private List<IRNode> nodes = new ArrayList<>();
    private Stack<Frame> frameStack = new Stack<>();

    @Override
    public TranslatedExpression visit(Program n) {
        for (int i = 0; i < n.classDeclList.size(); i++) {
            n.classDeclList.elementAt(i).accept(this);
        }

        return n.mainClass.accept(this);
    }

    @Override
    public TranslatedExpression visit(MainClass n) {
        Frame newFrame = frameStack.peek().newFrame(Translator.MAIN, new ArrayList<Boolean>());
        frameStack.push(newFrame);

        currentClassName = n.i1.s;
        currentMethodName = "main";

        nodes.add(onMethodExit(n.s.accept(this)));

        currentClassName = null;
        currentMethodName = null;
        frameStack.pop();

        return null;
    }

    @Override
    public TranslatedExpression visit(ClassDeclSimple n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(ClassDeclExtends n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(VarDecl n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(MethodDecl n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(Formal n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(IntArrayType n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(BooleanType n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(IntegerType n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(IdentifierType n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(Block n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(If n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(While n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(Print n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(Assign n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(ArrayAssign n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(And n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(LessThan n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(Plus n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(Minus n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(Times n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(ArrayLookup n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(ArrayLength n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(Call n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(IntegerLiteral n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(True n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(False n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(IdentifierExp n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(This n) {
        return null;
    }

    @Override
    public TranslatedExpression visit(NewArray n) {
        IRNode register = IR.TEMP(new Temp());
        IRNode call = IR.CALL(Translator.NEW_ARRAY, n.e.accept(this).unEx());
        IRNode move = IR.MOVE(register, call);

        return new TranslateEx(IR.ESEQ(move, register));
    }

    @Override
    public TranslatedExpression visit(NewObject n) {
        int wordSize = frameStack.peek().getWordSize();
        int bytes = (MAX_CLASS_LENGTH + 1) * wordSize;

        IRNode temp = IR.TEMP(new Temp());
        IRNode call = IR.CALL(Translator.NEW_OBJECT, IR.CONST(bytes));
        IRNode sequence = IR.MOVE(temp, call);

        // Heap initialization for the new object and all its internal attributes.
        for (int i = 0; i < MAX_CLASS_LENGTH; i++) {
            sequence = IR.SEQ(sequence, IR.MOVE(IR.MEM(IR.BINOP(Operator.PLUS, temp, IR.CONST(i * wordSize))), IR.CONST(0)));
        }
        return new TranslateEx(IR.ESEQ(sequence, temp));
    }

    @Override
    public TranslatedExpression visit(Not n) {
        return new TranslateEx(IR.BINOP(Operator.MINUS, IR.CONST(1), n.e.accept(this).unEx()));
    }

    @Override
    public TranslatedExpression visit(Identifier n) {
        return null;
    }

    // Updates the current frame's state when exiting a method.
    private IRNode onMethodExit(TranslatedExpression body) {
        Frame currentFrame = frameStack.peek();
        IRNode statement = IR.NOP;

        if (body != null) {
            IRNode expression = body.unEx();

            if (expression != null) {
                statement = IR.MOVE(IR.TEMP(currentFrame.getReturnValuePointer()), expression);
            } else {
                statement = body.unNx();
            }
        }

        return currentFrame.onMethodExit(statement);
    }
}
